﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Xml.XPath;

namespace Quail
{
    public class BasicXmlNode : IXmlNode
    {
        private readonly XPathNavigator _node;
        private Dictionary<string, IXmlNode> _hashControls;
        private Dictionary<string, List<IXmlNode>> _hashControlsList;
        private bool _navigateAttached;

        public BasicXmlNode(IXmlNode parent, XPathNavigator node, string selector)
        {
            _node = node;
            MySelector = selector;
            ParentNode = parent;
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="IQuailControl"/> is exists.
        /// </summary>
        /// <value><c>true</c> if exists; otherwise, <c>false</c>.</value>
        public bool Exists
        {
            get { return ParentNode.GetNode(MySelector) != null; }
        }

        /// <summary>
        /// Gets or sets the selector.
        /// </summary>
        /// <value>The selector.</value>
        public string Selector
        {
            get { return ParentNode.Selector + MySelector; }
            set { throw new NotImplementedException(); }
        }

        /// <summary>
        /// Gets my selector
        /// </summary>
        public string MySelector { get; private set; }

        /// <summary>
        /// Gets the page.
        /// </summary>
        /// <value>The page.</value>
        public BasicWebPage Page { get { return ParentNode.Page; }}

        /// <summary>
        /// Gets the browser.
        /// </summary>
        /// <value>The browser.</value>
        public BasicWebBrowser Browser
        {
            get { return Page.Browser; }
        }

        /// <summary>
        /// Gets the active task.
        /// </summary>
        /// <value>The active task.</value>
        public Task ActiveTask
        {
            get { return Browser.ActiveTask; }
        }

        /// <summary>
        /// Gets the parent control.
        /// </summary>
        /// <value>The parent control.</value>
        public IXmlNode ParentNode
        {
            get; private set;
        }

        /// <summary>
        /// This event is fired when the control should release any cached objects
        /// </summary>
        public event OnClearCacheEvent OnClearCache;

        private void AttachNavigateListener()
        {
            if (!_navigateAttached)
            {
                Browser.OnAfterNavigate += AfterBrowserNavigate;
                _navigateAttached = true;
            }
        }

        private void AfterBrowserNavigate(BasicWebRequest webrequest, HttpResponseMessage currentresponse)
        {
            _hashControlsList = null;
            _hashControls = null;
            Browser.OnAfterNavigate -= AfterBrowserNavigate;
            _navigateAttached = false;

            if (OnClearCache != null)
                OnClearCache();
        }

        private IXmlNode GetControl(string selector, Func<XPathNavigator, IXmlNode> creator)
        {
            if (_hashControls == null)
            {
                _hashControls = new Dictionary<string, IXmlNode>();
                AttachNavigateListener();
            }

            IXmlNode result;

            if (!_hashControls.TryGetValue(selector, out result))
            {
                var node = Browser.GetNode(_node, selector);
                result = creator(node);
            }

            return result;
        }

        private IEnumerable<IXmlNode> GetControls(string selector, Func<IEnumerable<XPathNavigator>, List<IXmlNode>> creator)
        {
            if (_hashControlsList == null)
                _hashControlsList = new Dictionary<string, List<IXmlNode>>();

            List<IXmlNode> result;

            if (!_hashControlsList.TryGetValue(selector, out result))
            {
                var node = Browser.GetNodes(_node, selector);
                result = creator(node);
            }

            return result;
        }

        /// <summary>
        /// Get a node from the XML based on the given selector
        /// </summary>
        /// <param name="selector">The XML selector</param>
        /// <returns>The node matching the given selector in the XML</returns>
        public IXmlNode GetNode(string selector)
        {
            return GetControl(selector, n => new BasicXmlNode(this, n, selector));
        }

        /// <summary>
        /// Get a node from the XML based on the given selector
        /// </summary>
        /// <param name="selector">The XML selector</param>
        /// <returns>The node matching the given selector in the XML</returns>
        public IEnumerable<IXmlNode> GetNodes(string selector)
        {
            return GetControls(selector, ns => ns.Select(n => new BasicXmlNode(this, n, selector)).Cast<IXmlNode>().ToList());
        }

        /// <summary>
        /// Get a list of items from the XML based on the given selector
        /// </summary>
        /// <param name="selector">The XML selector</param>
        /// <param name="creator">Creator method</param>
        /// <returns>The node matching the given selector in the XML</returns>
        public List<T> GetList<T>(string selector, Func<XPathNavigator, string, T> creator)
        {
            return Browser.GetNodes(_node, selector)
                .Select(n => creator(n, selector))
                .ToList();
        }

        /// <summary>
        /// Get a value from the XML based on the given selector
        /// </summary>
        /// <typeparam name="T">The type of the value to get</typeparam>
        /// <param name="selector">The XML selector</param>
        /// <returns>The value of the given selector in the XML</returns>
        public T GetValue<T>(string selector)
        {
            return Browser.GetValue<T>(_node, selector);
        }

        /// <summary>
        /// Get a value from the XML of the given node
        /// </summary>
        /// <typeparam name="T">The type of the value to get - string, int, decimal or bool</typeparam>
        /// <returns>The value of the given selector in the XML</returns>
        public T GetValue<T>()
        {
            return Browser.GetValue<T>(_node);
        }

        /// <summary>
        /// Get a list of string values found with the given xml element name
        /// </summary>
        /// <param name="selector">The XPath selector</param>
        /// <returns>The list of strings</returns>
        public List<T> GetValues<T>(string selector)
        {
            return Browser.GetValues<T>(_node, selector);
        }
    }
}
